﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Specialized;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml;

namespace Hbrs.Soa.Restclient.Serializing
{
    public class SchemaSerializer
    {
        #region SchemaCompilationResult

        class SchemaCompilationResult
        {
            #region public properties

            public bool HasErrors { get; set; }

            #endregion

            #region public methods

            public void ValidationCallbackWithErrorCode(object sender, ValidationEventArgs args)
            {
                string msg = null;
                if ((args.Exception.LineNumber == 0) && (args.Exception.LinePosition == 0))
                {
                    msg = "Schema validation warning: " + args.Message;
                }
                else
                {
                    msg = "Schema validation warning: "
                        + args.Message + " at "
                        + args.Exception.LineNumber
                        + String.Format(
                        CultureInfo.InvariantCulture,
                        "({0})",
                        args.Exception.LinePosition);
                }

                switch (args.Severity)
                {
                    case XmlSeverityType.Error:
                        this.HasErrors = true;
                        Trace.TraceError(msg);
                        break;
                    case XmlSeverityType.Warning:
                        Trace.TraceWarning(msg);
                        break;
                    default:
                        Trace.TraceInformation(msg);
                        break;
                }
            }

            #endregion
        }

        #endregion

        #region private methods

        private static XmlSchema ReadSchema(string location)
        {
            if (!File.Exists(location))
            {
                throw new FileNotFoundException("Schema file not found: " + location);
            }

            XmlTextReader reader = new XmlTextReader(location, new StreamReader(location));
            reader.XmlResolver = null;
            SchemaCompilationResult scr = new SchemaCompilationResult();
            XmlSchema schema = XmlSchema.Read(reader, new ValidationEventHandler(scr.ValidationCallbackWithErrorCode));
            if (scr.HasErrors)
            {
                throw new InvalidOperationException("Resulting schema cannot be verified.");
            }
            return schema;
        }

        private static void ImportSchemaAsClasses(XmlSchema schema, XmlSchemaImporter schemaImporter, XmlCodeExporter codeExporter)
        {
            if (schema != null)
            {
                ArrayList list = new ArrayList();
                foreach (XmlSchemaElement element in schema.Elements.Values)
                {
                    if (!element.IsAbstract)
                    {
                        list.Add(schemaImporter.ImportTypeMapping(element.QualifiedName));
                    }
                }
                foreach (XmlTypeMapping mapping in list)
                {
                    codeExporter.ExportTypeMapping(mapping);
                }
            }
        }

        private static Uri ResolveUri(Uri baseUri, string relativeUri)
        {
            if ((baseUri == null) || (!baseUri.IsAbsoluteUri && (baseUri.OriginalString.Length == 0)))
            {
                Uri uri = new Uri(relativeUri, UriKind.RelativeOrAbsolute);
                if (!uri.IsAbsoluteUri)
                {
                    uri = new Uri(Path.GetFullPath(relativeUri));
                }
                return uri;
            }
            if ((relativeUri != null) && (relativeUri.Length != 0))
            {
                return new Uri(baseUri, relativeUri);
            }
            return baseUri;
        }

        private static string GetPathFromUri(Uri uri)
        {
            if (uri != null)
            {
                return Path.GetFullPath(uri.LocalPath).ToUpperInvariant();
            }
            return null;
        }

        private static void CollectIncludes(XmlSchema schema, Uri baseUri, Hashtable includeSchemas, string topUri)
        {
            if (schema != null)
            {
                foreach (XmlSchemaExternal external in schema.Includes)
                {
                    string schemaLocation = external.SchemaLocation;
                    if (external is XmlSchemaImport)
                    {
                        external.SchemaLocation = null;
                        continue;
                    }
                    if (((external.Schema == null) && (schemaLocation != null)) && (schemaLocation.Length > 0))
                    {
                        Uri uri = ResolveUri(baseUri, schemaLocation);
                        string str2 = uri.ToString().ToUpperInvariant();
                        if (topUri == str2)
                        {
                            external.Schema = new XmlSchema();
                            external.Schema.TargetNamespace = schema.TargetNamespace;
                            external.SchemaLocation = null;
                            break;
                        }
                        XmlSchema schema2 = (XmlSchema)includeSchemas[str2];
                        if (schema2 == null)
                        {
                            string pathFromUri = GetPathFromUri(uri);
                            schema2 = ReadSchema(pathFromUri);
                            includeSchemas[str2] = schema2;
                            CollectIncludes(schema2, uri, includeSchemas, topUri);
                        }

                        if (schema2 != null)
                        {
                            external.Schema = schema2;
                            external.SchemaLocation = null;
                        }
                    }
                }
            }
        }

        private static SchemaCompilationResult Compile(XmlSchemas userSchemas, Hashtable uris, Hashtable includeSchemas)
        {
            foreach (XmlSchema schema in userSchemas)
            {
                if ((schema.TargetNamespace != null) && (schema.TargetNamespace.Length == 0))
                {
                    schema.TargetNamespace = null;
                }
                Uri baseUri = (Uri)uris[schema];
                CollectIncludes(schema, baseUri, includeSchemas, baseUri.ToString().ToUpperInvariant());
            }
            SchemaCompilationResult scr = new SchemaCompilationResult();
            userSchemas.Compile(new ValidationEventHandler(scr.ValidationCallbackWithErrorCode), true);
            return scr;
        }

        private static CodeNamespace ImportSchemas(string ns, XmlSchemas userSchemas, Hashtable includeSchemas)
        {
            CodeNamespace typeNamespace = new CodeNamespace(ns);
            XmlCodeExporter codeExporter = new XmlCodeExporter(typeNamespace);
            XmlSchemaImporter schemaImporter = new XmlSchemaImporter(
                userSchemas,
                CodeGenerationOptions.GenerateProperties,
                new ImportContext(new CodeIdentifiers(), false)
            );
            schemaImporter.Extensions.Add(new DataSetSchemaImporterExtension());

            typeNamespace.Imports.Add(new CodeNamespaceImport(typeof(XmlAttributeAttribute).Namespace));
            foreach (XmlSchema userSchema in userSchemas)
            {
                ImportSchemaAsClasses(userSchema, schemaImporter, codeExporter);
            }
            foreach (XmlSchema includeSchema in includeSchemas.Values)
            {
                ImportSchemaAsClasses(includeSchema, schemaImporter, codeExporter);
            }
            return typeNamespace;
        }

        #endregion

        #region public methods

        public static CodeNamespace ImportSchemasAsClasses(string inputFileName, string ns)
        {
            XmlSchemas userSchemas = new XmlSchemas();
            string location = Path.GetFullPath(inputFileName).ToUpperInvariant();
            XmlSchema schema = ReadSchema(location);
            Uri uri2 = new Uri(location);
            userSchemas.Add(schema, uri2);

            Hashtable uris = new Hashtable();
            uris.Add(schema, uri2);

            Hashtable includeSchemas = new Hashtable();
            SchemaCompilationResult scr = Compile(userSchemas, uris, includeSchemas);
            if (scr.HasErrors)
            {
                throw (new InvalidOperationException("The specified schema cannot be compiled."));
            }

            CodeNamespace typeNamespace = ImportSchemas(ns, userSchemas, includeSchemas);
            CodeTypeDeclarationCollection types = typeNamespace.Types;
            if ((types != null) && (types.Count > 0))
            {
                CodeGenerator.ValidateIdentifiers(typeNamespace);
            }
            else
            {
                Trace.TraceWarning("No classes have been generated.");
            }
            return typeNamespace;
        }

        public static XmlSchemas WriteSchemaFromType(Type type)
        {
            XmlReflectionImporter importer = new XmlReflectionImporter();
            XmlSchemas schemas = new XmlSchemas();
            XmlSchemaExporter exporter = new XmlSchemaExporter(schemas);

            if ((type.IsPublic && (!type.IsAbstract || !type.IsSealed)) && (!type.IsInterface && !type.ContainsGenericParameters))
            {
                XmlTypeMapping xmlTypeMapping = importer.ImportTypeMapping(type);
                exporter.ExportTypeMapping(xmlTypeMapping);
            }
            else
            {
                throw (new ArgumentException(
                    String.Format(
                    CultureInfo.InvariantCulture,
                    "The specified type {0} cannot be serialized to a schema. Make sure that the type is neither abstract nor sealed, and is not an interface.",
                    type.FullName
                    )));
            }
            SchemaCompilationResult scr = new SchemaCompilationResult();
            schemas.Compile(new ValidationEventHandler(scr.ValidationCallbackWithErrorCode), false);
            if (scr.HasErrors)
            {
                throw new InvalidOperationException("Resulting schema cannot be verified.");
            }
            return schemas;
        }

        #endregion
    }
}
